#pragma once
#ifndef CLIB_RWLOCK_H
#define CLIB_RWLOCK_H

#include "clib_type.h"
#include <stdatomic.h>

#ifdef __cplusplus
extern "C" {
#endif

#define clib_rw_lock_wait	1
#define clib_rw_lock_write	2
#define clib_rw_lock_read	4

#define clib_rw_lock_mask (clib_rw_lock_wait|clib_rw_lock_write)

typedef struct {
	volatile i32_t rw_lock;
} clib_rwlock_t;

static inline void
clib_rwlock_init(clib_rwlock_t* rw) 
{
	rw->rw_lock = 0;
}

static inline void
clib_rwlock_read_lock(clib_rwlock_t* rw)
{
	i32_t lock;

	while(1) {
		
		if(__atomic_load_n(&rw->rw_lock,__ATOMIC_RELAXED) & clib_rw_lock_mask) {
			//__atomic_thread_fence(__ATOMIC_ACQUIRE);
			continue;
		}

		lock = __atomic_add_fetch(&rw->rw_lock, clib_rw_lock_read, __ATOMIC_ACQUIRE);
		
		if(!(lock & clib_rw_lock_mask)) {
			return ;
		}

		__atomic_fetch_sub(&rw->rw_lock, clib_rw_lock_read, __ATOMIC_RELAXED);
	}
}

static inline i32_t
clib_rwlock_read_trylock(clib_rwlock_t* rw) 
{
	i32_t lock;

	lock = __atomic_load_n(&rw->rw_lock, __ATOMIC_RELAXED);

	if(lock & clib_rw_lock_mask) {
		return -1;
	}

	lock = __atomic_add_fetch(&rw->rw_lock, clib_rw_lock_read, __ATOMIC_ACQUIRE);
	if(lock & clib_rw_lock_mask) {
		__atomic_fetch_sub(&rw->rw_lock, clib_rw_lock_read, __ATOMIC_RELAXED);
		return -1;
	}
		
	return 0;
}

static inline void
clib_rwlock_read_unlock(clib_rwlock_t* rw) 
{
	__atomic_fetch_sub(&rw->rw_lock, clib_rw_lock_read, __ATOMIC_RELAXED);
}

static inline void
clib_rwlock_write_lock(clib_rwlock_t* rw)
{
	i32_t lock;
	while(1) {
		lock = __atomic_load_n(&rw->rw_lock, __ATOMIC_RELAXED);

		if(lock < clib_rw_lock_write) {
			if (__atomic_compare_exchange_n(&rw->rw_lock, &lock, clib_rw_lock_write, 1,
							__ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
				return;
			}	
		}

		if (!(lock & clib_rw_lock_wait)) {
			__atomic_fetch_or(&rw->rw_lock, clib_rw_lock_wait, __ATOMIC_RELAXED);
		}
		
		while (__atomic_load_n(&rw->rw_lock, __ATOMIC_RELAXED) > clib_rw_lock_wait) {
			__atomic_thread_fence(__ATOMIC_ACQUIRE);
		}
	}
}

static inline i32_t
clib_rwlock_write_trylock(clib_rwlock_t* rw) 
{
	i32_t lock;

	lock = __atomic_load_n(&rw->rw_lock, __ATOMIC_RELAXED);

	if(lock < clib_rw_lock_write) {
		if(__atomic_compare_exchange_n(&rw->rw_lock, &lock, lock + clib_rw_lock_write,
					1, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
			return 0;
		}
	}

	return -1;
}

static inline void
clib_rwlock_write_unlock(clib_rwlock_t* rw)
{
	__atomic_fetch_sub(&rw->rw_lock, clib_rw_lock_write, __ATOMIC_RELEASE);
}

#ifdef __cplusplus
}
#endif

#endif